/**
* Copyright 2007-2015, Kaazing Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaazing.k3po.junit.rules;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import org.junit.AssumptionViolatedException;
import org.junit.ComparisonFailure;
import org.junit.runners.model.Statement;
import org.kaazing.k3po.junit.rules.internal.ScriptPair;
final class SpecificationStatement extends Statement {
private final Statement statement;
private final Latch latch;
private final ScriptRunner scriptRunner;
SpecificationStatement(Statement statement, URL controlURL, List<String> scriptNames, Latch latch,
List<String> overridenScriptProperties) {
this.statement = statement;
this.latch = latch;
this.scriptRunner = new ScriptRunner(controlURL, scriptNames, latch, overridenScriptProperties);
}
@Override
public void evaluate() throws Throwable {
latch.setInterruptOnException(Thread.currentThread());
FutureTask<ScriptPair> scriptFuture = new FutureTask<>(scriptRunner);
try {
// start the script execution
new Thread(scriptFuture).start();
// wait for script to be prepared (all binds ready for incoming connections from statement)
latch.awaitPrepared();
try {
// note: JUnit timeout will trigger an exception
statement.evaluate();
} catch (AssumptionViolatedException e) {
if (!latch.isFinished()) {
scriptRunner.abort();
}
throw e;
} catch (Throwable cause) {
// any exception aborts the script (including timeout)
if (latch.hasException()) {
// propagate exception if the latch has an exception
throw cause;
} else {
// It is possible that the script is finished even if we get an exception
// in particular a timeout may occur but the script may finish before we actually
// process it the timeout, send the abort and get the result back.
// No reason to send the abort if we are already finished.
// Note that there is a race in that the script may be finished before
// we actually send the abort. But that is ok.
if (!latch.isFinished()) {
scriptRunner.abort();
}
try {
// wait at most 5sec for the observed script (due to the abort case)
// should take less than a second for K3PO to complete
ScriptPair scripts = scriptFuture.get(5, SECONDS);
try {
assertEquals("Specified behavior did not match", scripts.getExpectedScript(),
scripts.getObservedScript());
// Throw the original exception if we are equal
throw cause;
} catch (ComparisonFailure f) {
// throw an exception that highlights the difference in behavior, but caused by the timeout
// (or
// original exception)
f.initCause(cause);
throw f;
}
} catch (ExecutionException ee) {
throw ee.getCause().initCause(cause);
} catch (Exception e) {
// Note that ComparisonFailure are not an Exception we do want those to bubble up.
throw cause;
}
}
}
// note: statement MUST call join() to ensure wrapped Rule(s) do not complete early
// and to allow Specification script(s) to make progress
String k3poSimpleName = K3poRule.class.getSimpleName();
assertTrue(format("Did you instantiate %s with a @Rule and call %s.join()?", k3poSimpleName, k3poSimpleName),
latch.isStartable());
ScriptPair scripts = scriptFuture.get();
assertEquals("Specified behavior did not match", scripts.getExpectedScript(), scripts.getObservedScript());
} finally {
try {
scriptRunner.dispose();
} finally {
// clean up the task if it is still running
scriptFuture.cancel(true);
}
}
}
public void awaitBarrier(String barrierName) throws InterruptedException {
scriptRunner.awaitBarrier(barrierName);
}
public void notifyBarrier(String barrierName) throws InterruptedException {
scriptRunner.notifyBarrier(barrierName);
}
}